package thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 synchronized 关键字，它包括4种用法：synchronized函数,synchronized代码块,特殊域变量volatile和重入锁。
 1.同步代码块语法
     synchronized (对象) {               //对象如同"锁"
        ...需要被同步的代码
     }
 2.同步函数
     private synchronized void show(){  //同步函数使用的"锁"是this;
        ...需要被同步的代码              //如果被静态修饰"锁"是类名.class,对象类型是class
     }
 3 使用特殊域变量(volatile)实现线程同步
     private volatile int count = 1000;
     a.volatile关键字为域变量的访问提供了一种免锁机制，
     　　b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
     　　c.因此每次使用该域就要重新计算，而不是使用寄存器中的值
     　　d.volatile不会提供任何原子操作，它也不能用来修饰final类型的变量

 4.使用互斥锁实现线程同步
     new ReentrantLock();   // 创建一个ReentrantLock实例(重入锁)
     lock()                 // 获得锁
     unlock()               // 释放锁

 同步前提:
 1.必须要有2个或2个以上线程    2.必须是多个线程使用同一个锁

 如何找问题:
     1.明确那些代码是多线程运行代码
     2.明确共享数据
     3.明确多线程运行代码中那些语句是操作共享数据的
 */
public class Demo02Synchronized {
    public static void main(String[] args) {

        /*---同步代码块------------------*/
//        //创建Runnable接口的实现类对象
//        RunnableImpl run = new RunnableImpl();
//        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
//        Thread t0 = new Thread(run);
//        Thread t1 = new Thread(run);
//        //调用start方法开启多线程
//        t0.start();
//        t1.start();

        /*---同步方法---------------------*/
//        RunnableImpl1 run1=new RunnableImpl1();
//        Thread t2 = new Thread(run1);
//        Thread t3 = new Thread(run1);
//        Thread t4 = new Thread(run1);
//        //调用start方法开启多线程
//        t2.start();
//        t3.start();
//        t4.start();

        /*---锁机制------------------------*/
        RunnableImpl2 run2=new RunnableImpl2();
        Thread t5 = new Thread(run2);
        Thread t6 = new Thread(run2);
        Thread t7 = new Thread(run2);
        //调用start方法开启多线程
        t5.start();
        t6.start();
        t7.start();
    }
}

/**
 * synchronized同步代码块
 */
class RunnableImpl implements Runnable{

    private int ticket = 1000; //定义多个线程共享的票源
    //创建一个锁对象
    Object obj = new Object();
    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环,让卖票操作重复执行
        while(true){
            synchronized (obj){  //同步代码块
                //先判断票是否存在
                if(ticket>0){
                    try {
                        Thread.sleep(1);//提高安全问题出现的概率,让程序睡眠
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}

/**
 * synchronized同步方法
 */
class RunnableImpl1 implements Runnable{

    private static int ticket = 1000; //定义多个线程共享的票源
    @Override
    public void run() {
        while(true) payTicketStatic();
    }

    private synchronized void payTicketStatic() {
        if(ticket>0){
            //提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
            ticket--;
        }else{
            return;
        }

    }
}

/**
 * 锁机制
 */
class RunnableImpl2 implements Runnable{

    private static int ticket = 1000; //定义多个线程共享的票源
    //1.在成员位置创建一个ReentrantLock对象
    Lock mLock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            mLock.lock();
            if(ticket>0){
                try {
                    Thread.sleep(1);
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    mLock.unlock();//无论程序是否异常,都会把锁释放
                }
            }

        }
    }

}
